<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_35</span></h2>

  <p>”（以前可用“private protected”实现这个目的，但这一对关键字的组合已被取消了）。</p>

  <p>(28) 嵌套的类。在C++中，对类进行嵌套有助于隐藏名称，并便于代码的组织（但C++的“命名空间”已使名称的隐藏显得多余）。Java的“封装”或“打包”概念等价于C++的命名空间，所以不再是一个问题。Java 1.1引入了“内部类”的概念，它秘密保持指向外部类的一个句柄――创建内部类对象的时候需要用到。这意味着内部类对象也许能访问外部类对象的成员，毋需任何条件――就好象那些成员直接隶属于内部类对象一样。这样便为回调问题提供了一个更优秀的方案――C++是用指向成员的指针解决的。</p>

  <p>(29) 由于存在前面介绍的那种内部类，所以Java里没有指向成员的指针。</p>

  <p>(30) Java不存在“嵌入”（inline）方法。Java编译器也许会自行决定嵌入一个方法，但我们对此没有更多的控制权力。在Java中，可为一个方法使用final关键字，从而“建议”进行嵌入操作。然而，嵌入函数对于C++的编译器来说也只是一种建议。</p>

  <p>(31) Java中的继承具有与C++相同的效果，但采用的语法不同。Java用extends关键字标志从一个基础类的继承，并用super关键字指出准备在基础类中调用的方法，它与我们当前所在的方法具有相同的名字（然而，Java中的super关键字只允许我们访问父类的方法――亦即分级结构的上一级）。通过在C++中设定基础类的作用域，我们可访问位于分级结构较深处的方法。亦可用super关键字调用基础类构建器。正如早先指出的那样，所有类最终都会从Object里自动继承。和C++不同，不存在明确的构建器初始化列表。但编译器会强迫我们在构建器主体的开头进行全部的基础类初始化，而且不允许我们在主体的后面部分进行这一工作。通过组合运用自动初始化以及来自未初始化对象句柄的异常，成员的初始化可得到有效的保证。</p>

  <p>public class Foo extends Bar {</p>

  <p>public Foo(String msg) {</p>

  <p>super(msg); // Calls base constructor</p>

  <p>}</p>

  <p>public baz(int i) { // Override</p>

  <p>super.baz(i); // Calls base method</p>

  <p>}</p>

  <p>}</p>

  <p>(32) Java中的继承不会改变基础类成员的保护级别。我们不能在Java中指定public，private或者protected继承，这一点与C++是相同的。此外，在衍生类中的优先方法不能减少对基础类方法的访问。例如，假设一个成员在基础类中属于public，而我们用另一个方法代替了它，那么用于替换的方法也必须属于public（编译器会自动检查）。</p>

  <p>(33) Java提供了一个interface关键字，它的作用是创建抽象基础类的一个等价物。在其中填充抽象方法，且没有数据成员。这样一来，对于仅仅设计成一个接口的东西，以及对于用extends关键字在现有功能基础上的扩展，两者之间便产生了一个明显的差异。不值得用abstract关键字产生一种类似的效果，因为我们不能创建属于那个类的一个对象。一个abstract（抽象）类可包含抽象方法（尽管并不要求在它里面包含什么东西），但它也能包含用于具体实现的代码。因此，它被限制成一个单一的继承。通过与接口联合使用，这一方案避免了对类似于C++虚拟基础类那样的一些机制的需要。</p>

  <p>为创建可进行“例示”（即创建一个实例）的一个interface（接口）的版本，需使用implements关键字。它的语法类似于继承的语法，如下所示：</p>

  <p>public interface Face {</p>

  <p>public void smile();</p>

  <p>}</p>

  <p>public class Baz extends Bar implements Face {</p>

  <p>public void smile( ) {</p>

  <p>System.out.println("a warm smile");</p>

  <p>}</p>

  <p>}</p>

  <p>(34) Java中没有virtual关键字，因为所有非static方法都肯定会用到动态绑定。在Java中，程序员不必自行决定是否使用动态绑定。C++之所以采用了virtual，是由于我们对性能进行调整的时候，可通过将其省略，从而获得执行效率的少量提升（或者换句话说：“如果不用，就没必要为它付出代价”）。virtual经常会造成一定程度的混淆，而且获得令人不快的结果。final关键字为性能的调整规定了一些范围――它向编译器指出这种方法不能被取代，所以它的范围可能被静态约束（而且成为嵌入状态，所以使用C++非virtual调用的等价方式）。这些优化工作是由编译器完成的。</p>

  <p>(35) Java不提供多重继承机制（MI），至少不象C++那样做。与protected类似，MI表面上是一个很不错的主意，但只有真正面对一个特定的设计问题时，才知道自己需要它。由于Java使用的是“单根”分级结构，所以只有在极少的场合才需要用到MI。interface关键字会帮助我们自动完成多个接口的合并工作。</p>

  <p>(36) 运行期的类型标识功能与C++极为相似。例如，为获得与句柄X有关的信息，可使用下述代码：</p>

  <p>X.getClass().getName();</p>

  <p>为进行一个“类型安全”的紧缩造型，可使用：</p>

  <p>derived d = (derived)base;</p>

  <p>这与旧式风格的C造型是一样的。编译器会自动调用动态造型机制，不要求使用额外的语法。尽管它并不象C++的“new casts”那样具有易于定位造型的优点，但Java会检查使用情况，并丢弃那些“异常”，所以它不会象C++那样允许坏造型的存在。</p>

  <p>(37) Java采取了不同的异常控制机制，因为此时已经不存在构建器。可添加一个finally从句，强制执行特定的语句，以便进行必要的清除工作。Java中的所有异常都是从基础类Throwable里继承而来的，所以可确保我们得到的是一个通用接口。</p>

  <p>public void f(Obj b) throws IOException {</p>

  <p>myresource mr = b.createResource();</p>

  <p>try {</p>

  <p>mr.UseResource();</p>

  <p>} catch (MyException e) {</p>

  <p>// handle my exception</p>

  <p>} catch (Throwable e) {</p>

  <p>// handle all other exceptions</p>

  <p>} finally {</p>

  <p>mr.dispose(); // special cleanup</p>

  <p>}</p>

  <p>}</p>

  <p>(38) Java的异常规范比C++的出色得多。丢弃一个错误的异常后，不是象C++那样在运行期间调用一个函数，Java异常规范是在编译期间检查并执行的。除此以外，被取代的方法必须遵守那一方法的基础类版本的异常规范：它们可丢弃指定的异常或者从那些异常衍生出来的其他异常。这样一来，我们最终得到的是更为“健壮”的异常控制代码。</p>

  <p>(39) Java具有方法过载的能力，但不允许运算符过载。String类不能用+和+=运算符连接不同的字串，而且String表达式使用自动的类型转换，但那是一种特殊的内建情况。</p>

  <p>(40) 通过事先的约定，C++中经常出现的const问题在Java里已得到了控制。我们只能传递指向对象的句柄，本地副本永远不会为我们自动生成。若希望使用类似C++按值传递那样的技术，可调用clone()，生成自变量的一个本地副本（尽管clone()的设计依然尚显粗糙――参见第12章）。根本不存在被自动调用的副本构建器。为创建一个编译期的常数值，可象下面这样编码：</p>

  <p>static final int SIZE = 255</p>

  <p>static final int BSIZE = 8 * SIZE</p>

  <p>(41) 由于安全方面的原因，“应用程序”的编程与“程序片”的编程之间存在着显著的差异。一个最明显的问题是程序片不允许我们进行磁盘的写操作，因为这样做会造成从远程站点下载的、不明来历的程序可能胡乱改写我们的磁盘。随着Java 1.1对数字签名技术的引用，这一情况已有所改观。根据数字签名，我们可确切知道一个程序片的全部作者，并验证他们是否已获得授权。Java 1.2会进一步增强程序片的能力。</p>

  <p>(42) 由于Java在某些场合可能显得限制太多，所以有时不愿用它执行象直接访问硬件这样的重要任务。Java解决这个问题的方案是“固有方法”，允许我们调用由其他语言写成的函数（目前只支持C和C++）。这样一来，我们就肯定能够解决与平台有关的问题（采用一种不可移植的形式，但那些代码随后会被隔离起来）。程序片不能调用固有方法，只有应用程序才可以。</p>

  <p>(43) Java提供对注释文档的内建支持，所以源码文件也可以包含它们自己的文档。通过一个单独的程序，这些文档信息可以提取出来，并重新格式化成HTML。这无疑是文档管理及应用的极大进步。</p>

  <p>(44) Java包含了一些标准库，用于完成特定的任务。C++则依靠一些非标准的、由其他厂商提供的库。这些任务包括（或不久就要包括）：</p>

  <p>■连网</p>

  <p>■数据库连接（通过JDBC）</p>

  <p>■多线程</p>

  <p>■分布式对象（通过RMI和CORBA）</p>

  <p>■压缩</p>

  <p>■商贸</p>

  <p>由于这些库简单易用，而且非常标准，所以能极大加快应用程序的开发速度。</p>

  <p>(45) Java 1.1包含了Java Beans标准，后者可创建在可视编程环境中使用的组件。由于遵守同样的标准，所以可视组件能够在所有厂商的开发环境中使用。由于我们并不依赖一家厂商的方案进行可视组件的设计，所以组件的选择余地会加大，并可提高组件的效能。除此之外，Java Beans的设计非常简单，便于程序员理解；而那些由不同的厂商开发的专用组件框架则要求进行更深入的学习。</p>

  <p>(46) 若访问Java句柄失败，就会丢弃一次异常。这种丢弃测试并不一定要正好在使用一个句柄之前进行。根据Java的设计规范，只是说异常必须以某种形式丢弃。许多C++运行期系统也能丢弃那些由于指针错误造成的异常。</p>

  <p>(47) Java通常显得更为健壮，为此采取的手段如下：</p>

  <p>■对象句柄初始化成null（一个关键字）</p>

  <p>■句柄肯定会得到检查，并在出错时丢弃异常</p>

  <p>■所有数组访问都会得到检查，及时发现边界违例情况</p>

  <p>■自动垃圾收集，防止出现内存漏洞</p>

  <p>■明确、“傻瓜式”的异常控制机制</p>

  <p>■为多线程提供了简单的语言支持</p>

  <p>■对网络程序片进行字节码校验</p>

  <p>英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------附录C Java编程规则</p>

  <p>本附录包含了大量有用的建议，帮助大家进行低级程序设计，并提供了代码编写的一般性指导：</p>

  <p>(1) 类名首字母应该大写。字段、方法以及对象（句柄）的首字母应小写。对于所有标识符，其中包含的所有单词都应紧靠在一起，而且大写中间单词的首字母。例如：</p>

  <p>ThisIsAClassName</p>

  <p>thisIsMethodOrFieldName</p>

  <p>若在定义中出现了常数初始化字符，则大写static final基本类型标识符中的所有字母。这样便可标志出它们属于编译期的常数。</p>

  <p>Java包（Package）属于一种特殊情况：它们全都是小写字母，即便中间的单词亦是如此。对于域名扩展名称，如com，org，net或者edu等，全部都应小写（这也是Java 1.1和Java 1.2的区别之一）。</p>

  <p>(2) 为了常规用途而创建一个类时，请采取“经典形式”，并包含对下述元素的定义：</p>

  <p>equals()</p>

  <p>hashCode()</p>

  <p>toString()</p>

  <p>clone()（implement Cloneable）</p>

  <p>implement Serializable</p>

  <p>(3) 对于自己创建的每一个类，都考虑置入一个main()，其中包含了用于测试那个类的代码。为使用一个项目中的类，我们没必要删除测试代码。若进行了任何形式的改动，可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。</p>

  <p>(4) 应将方法设计成简要的、功能性单元，用它描述和实现一个不连续的类接口部分。理想情况下，方法应简明扼要。若长度很大，可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用（有些时候，方法必须非常大，但它们仍应只做同样的一件事情）。</p>

  <p>(5) 设计一个类时，请设身处地为客户程序员考虑一下（类的使用方法应该是非常明确的）。然后，再设身处地为管理代码的人考虑一下（预计有可能进行哪些形式的修改，想想用什么方法可把它们变得更简单）。</p>

  <p>(6) 使类尽可能短小精悍，而且只解决一个特定的问题。下面是对类设计的一些建议：</p>

  <p>■一个复杂的开关语句：考虑采用“多形”机制</p>

  <p>■数量众多的方法涉及到类型差别极大的操作：考虑用几个类来分别实现</p>

  <p>■许多成员变量在特征上有很大的差别：考虑使用几个类</p>

  <p>(7) 让一切东西都尽可能地“私有”――private。可使库的某一部分“公共化”（一个方法、类或者一个字段等等），就永远不能把它拿出。若强行拿出，就可能破坏其他人现有的代码，使他们不得不重新编写和设计。若只公布自己必须公布的，就可放心大胆地改变其他任何东西。在多线程环境中，隐私是特别重要的一个因素――只有private字段才能在非同步使用的情况下受到保护。</p>

  <p>(8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手，往往喜欢先写一个顺序执行的程序，再把它嵌入一个或两个巨大的对象里。根据编程原理，对象表达的应该是应用程序的概念，而非应用程序本身。</p>

  <p>(9) 若不得已进行一些不太雅观的编程，至少应该把那些代码置于一个类的内部。</p>

  <p>(10) 任何时候只要发现类与类之间结合得非常紧密，就需要考虑是否采用内部类，从而改善编码及维护工作（参见第14章14.1.2小节的“用内部类改进代码”）。</p>

  <p>(11) 尽可能细致地加上注释，并用javadoc注释文档语法生成自己的程序文档。</p>

  <p>(12) 避免使用“魔术数字”，这些数字很难与代码很好地配合。如以后需要修改它，无疑会成为一场噩梦，因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以，我们应创建一个常数，并为其使用具有说服力的描述性名称，并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。</p>

  <p>(13) 涉及构建器和异常的时候，通常希望重新丢弃在构建器中捕获的任何异常――如果它造成了那个对象的创建失败。这样一来，调用者就不会以为那个对象已正确地创建，从而盲目地继续。</p>

  <p>(14) 当客户程序员用完对象以后，若你的类要求进行任何清除工作，可考虑将清除代码置于一个良好定义的方法里，采用类似于cleanup()这样的名字，明确表明自己的用途。除此以外，可在类内放置一个boolean（布尔）标记，指出对象是否已被清除。在类的finalize()方法里，请确定对象已被清除，并已丢弃了从RuntimeException继承的一个类（如果还没有的话），从而指出一个编程错误。在采取象这样的方案之前，请确定finalize()能够在自己的系统中工作（可能需要调用System.runFinalizersOnExit(true)，从而确保这一行为）。</p>

  <p>(15) 在一个特定的作用域内，若一个对象必须清除（非由垃圾收集机制处理），请采用下述方法：初始化对象；若成功，则立即进入一个含有finally从句的try块，开始清除工作。</p>

  <p>(16) 若在初始化过程中需要覆盖（取消）finalize()，请记住调用super.finalize()（若Object属于我们的直接超类，则无此必要）。在对finalize()进行覆盖的过程中，对super.finalize()的调用应属于最后一个行动，而不应是第一个行动，这样可确保在需要基础类组件的时候它们依然有效。</p>

  <p>(17) 创建大小固定的对象集合时，请将它们传输至一个数组（若准备从一个方法里返回这个集合，更应如此操作）。这样一来，我们就可享受到数组在编译期进行类型检查的好处。此外，为使用它们，数组的接收者也许并不需要将对象“造型”到数组里。</p>

  <p>(18) 尽量使用interfaces，不要使用abstract类。若已知某样东西准备成为一个基础类，那么第一个选择应是将其变成一个interface（接口）。只有在不得不使用方法定义或者成员变量的时候，才需要将其变成一个abstract（抽象）类。接口主要描述了客户希望做什么事情，而一个类则致力于（或允许）具体的实施细节。</p>

  <p>(19) 在构建器内部，只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法，因为那些方法可能被其他人覆盖或取消，从而在构建过程中产生不可预知的结果（参见第7章的详细说明）。</p>

  <p>(20) 对象不应只是简单地容纳一些数据；它们的行为也应得到良好的定义。</p>

  <p>(21) 在现成类的基础上创建新类时，请首先选择“新建”或“创作”。只有自己的设计要求必须继承时，才应考虑这方面的问题。若在本来允许新建的场合使用了继承，则整个设计会变得没有必要地复杂。</p>

  <p>(22) 用继承及方法覆盖来表示行为间的差异，而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色，这是绝对应该避免的：应直接使用一个“颜色”字段。</p>

  <p>(23) 为避免编程时遇到麻烦，请保证在自己类路径指到的任何地方，每个名字都仅对应一个类。否则，编译器可能先找到同名的另一个类，并报告出错消息。若怀疑自己碰到了类路径问题，请试试在类路径的每一个起点，搜索一下同名的.class文件。</p>

  <p>(24) 在Java 1.1 AWT中使用事件“适配器”时，特别容易碰到一个陷阱。若覆盖了某个适配器方法，同时拼写方法没有特别讲究，最后的结果就是新添加一个方法，而不是覆盖现成方法。然而，由于这样做是完全合法的，所以不会从编译器或运行期系统获得任何出错提示――只不过代码的工作就变得不正常了。</p>

  <p>(25) 用合理的设计方案消除“伪功能”。也就是说，假若只需要创建类的一个对象，就不要提前限制自己使用应用程序，并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码，用于创建自己的对象，请考虑采纳一种创造性的方案，将些代码封装起来。</p>

  <p>(26) 警惕“分析瘫痪”。请记住，无论如何都要提前了解整个项目的状况，再去考察其中的细节。由于把握了全局，可快速认识自己未知的一些因素，防止在考察细节的时候陷入“死逻辑”中。</p>

  <p>(27) 警惕“过早优化”。首先让它运行起来，再考虑变得更快――但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候，才应进行优化。除非用专门的工具分析瓶颈，否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解，而且难于维护。</p>

  <p>(28) 请记住，阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序，但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己，还是对后来的人，它们都是相当重要的。如对此仍有怀疑，那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折，这样或许能将你说服。</p>

  <p>(29) 如认为自己已进行了良好的分析、设计或者实施，那么请稍微更换一下思维角度。试试邀请一些外来人士――并不一定是专家，但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作，看看是否能找出你一度熟视无睹的问题。采取这种方式，往往能在最适合修改的阶段找出一些关键性的问题，避免产品发行后再解决问题而造成的金钱及精力方面的损失。</p>

  <p>(30) 良好的设计能带来最大的回报。简言之，对于一个特定的问题，通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法，以后的工作就轻松多了，再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报（甚至无可估量）。而且由于自己倾注了大量心血，最终获得一个出色的设计方案，成功的快感也是令人心动的。坚持抵制草草完工的诱惑――那样做往往得不偿失。</p>

  <p>(31) 可在Web上找到大量的编程参考资源，甚至包括大量新闻组、讨论组、邮寄列表等。下面这个地方提供了大量有益的链接：</p>

  <p>http://www.ulb.ac.be/esp/ip-Links/Java/joodcs/mm-WebBiblio.html英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>附录D 性能</p>

  <p>“本附录由Joe Sharp投稿，并获得他的同意在这儿转载。请联系SharpJoe@aol.com”</p>

  <p>Java语言特别强调准确性，但可靠的行为要以性能作为代价。这一特点反映在自动收集垃圾、严格的运行期检查、完整的字节码检查以及保守的运行期同步等等方面。对一个解释型的虚拟机来说，由于目前有大量平台可供挑选，所以进一步阻碍了性能的发挥。</p>

  <p>“先做完它，再逐步完善。幸好需要改进的地方通常不会太多。”（Steve McConnell的《About performance》[16]）</p>

  <p>本附录的宗旨就是指导大家寻找和优化“需要完善的那一部分”。</p>

  <p>D.1 基本方法</p>

  <p>只有正确和完整地检测了程序后，再可着手解决性能方面的问题：</p>

  <p>(1) 在现实环境中检测程序的性能。若符合要求，则目标达到。若不符合，则转到下一步。</p>

  <p>(2) 寻找最致命的性能瓶颈。这也许要求一定的技巧，但所有努力都不会白费。如简单地猜测瓶颈所在，并试图进行优化，那么可能是白花时间。</p>

  <p>(3) 运用本附录介绍的提速技术，然后返回步骤1。</p>

  <p>为使努力不至白费，瓶颈的定位是至关重要的一环。Donald Knuth[9]曾改进过一个程序，那个程序把50％的时间都花在约4％的代码量上。在仅一个工作小时里，他修改了几行代码，使程序的执行速度倍增。此时，若将时间继续投入到剩余代码的修改上，那么只会得不偿失。Knuth在编程界有一句名言：“过早的优化是一切麻烦的根源”（Premature optimization is the root of all evil）。最明智的做法是抑制过早优化的冲动，因为那样做可能遗漏多种有用的编程技术，造成代码更难理解和操控，并需更大的精力进行维护。</p>

  <p>D.2 寻找瓶颈</p>

  <p>为找出最影响程序性能的瓶颈，可采取下述几种方法：</p>

  <p>D.2.1 安插自己的测试代码</p>

  <p>插入下述“显式”计时代码，对程序进行评测：</p>

  <p>long start = System.currentTimeMillis();</p>

  <p>// 要计时的运算代码放在这儿</p>

  <p>long time = System.currentTimeMillis() - start;</p>

  <p>利用System.out.println()，让一种不常用到的方法将累积时间打印到控制台窗口。由于一旦出错，编译器会将其忽略，所以可用一个“静态最终布尔值”（Static final boolean）打开或关闭计时，使代码能放心留在最终发行的程序里，这样任何时候都可以拿来应急。尽管还可以选用更复杂的评测手段，但若仅仅为了量度一个特定任务的执行时间，这无疑是最简便的方法。</p>

  <p>System.currentTimeMillis()返回的时间以千分之一秒（1毫秒）为单位。然而，有些系统的时间精度低于1毫秒（如Windows PC），所以需要重复n次，再将总时间除以n，获得准确的时间。</p>

  <p>D.2.2 JDK性能评测[2]</p>

  <p>JDK配套提供了一个内建的评测程序，能跟踪花在每个例程上的时间，并将评测结果写入一个文件。不幸的是，JDK评测器并不稳定。它在JDK 1.1.1中能正常工作，但在后续版本中却非常不稳定。</p>

  <p>为运行评测程序，请在调用Java解释器的未优化版本时加上-prof选项。例如：</p>

  <p>java_g -prof myClass</p>

  <p>或加上一个程序片（Applet）：</p>

  <p>java_g -prof sun.applet.AppletViewer applet.html</p>

  <p>理解评测程序的输出信息并不容易。事实上，在JDK 1.0中，它居然将方法名称截短为30字符。所以可能无法区分出某些方法。然而，若您用的平台确实能支持-prof选项，那么可试试Vladimir Bulatov的“HyperPorf”[3]或者Greg White的“ProfileViewer”来解释一下结果。</p>

  <p>D.2.3 特殊工具</p>

  <p>如果想随时跟上性能优化工具的潮流，最好的方法就是作一些Web站点的常客。比如由Jonathan Hardwick制作的“Tools for Optimizing Java”（Java优化工具）网站：</p>

  <p>http://www.cs.cmu.edu/~jch/java/tools.html</p>

  <p>D.2.4 性能评测的技巧</p>

  <p>■由于评测时要用到系统时钟，所以当时不要运行其他任何进程或应用程序，以免影响测试结果。</p>

  <p>■如对自己的程序进行了修改，并试图（至少在开发平台上）改善它的性能，那么在修改前后应分别测试一下代码的执行时间。</p>

  <p>■尽量在完全一致的环境中进行每一次时间测试。</p>

  <p>■如果可能，应设计一个不依赖任何用户输入的测试，避免用户的不同反应导致结果出现误差。</p>

  <p>D.3 提速方法</p>

  <p>现在，关键的性能瓶颈应已隔离出来。接下来，可对其应用两种类型的优化：常规手段以及依赖Java语言。</p>

  <p>D.3.1 常规手段</p>

  <p>通常，一个有效的提速方法是用更现实的方式重新定义程序。例如，在《Programming Pearls》（编程拾贝）一书中[14]，Bentley利用了一段小说数据描写，它可以生成速度非常快、而且非常精简的拼写检查器，从而介绍了Doug McIlroy对英语语言的表述。除此以外，与其他方法相比，更好的算法也许能带来更大的性能提升――特别是在数据集的尺寸越来越大的时候。欲了解这些常规手段的详情，请参考本附录末尾的“一般书籍”清单。</p>

  <p>D.3.2 依赖语言的方法</p>

  <p>为进行客观的分析，最好明确掌握各种运算的执行时间。这样一来，得到的结果可独立于当前使用的计算机――通过除以花在本地赋值上的时间，最后得到的就是“标准时间”。</p>

  <p>运算 示例 标准时间</p>

  <p>本地赋值 i=n; 1.0</p>

  <p>实例赋值 this.i=n; 1.2</p>

  <p>int增值 i++; 1.5</p>

  <p>byte增值 b++; 2.0</p>

  <p>short增值 s++; 2.0</p>

  <p>float增值 f++; 2.0</p>

  <p>double增值 d++; 2.0</p>

  <p>空循环 while(true) n++; 2.0</p>

  <p>三元表达式 (x&lt;0) ?-x : x 2.2</p>

  <p>算术调用 Math.abs(x); 2.5</p>

  <p>数组赋值 a[0] = n; 2.7</p>

  <p>long增值 l++; 3.5</p>

  <p>方法调用 funct(); 5.9</p>

  <p>throw或catch异常 try{ throw e; }或catch(e){} 320</p>

  <p>同步方法调用 synchMehod(); 570</p>

  <p>新建对象 new Object(); 980</p>

  <p>新建数组 new int[10]; 3100</p>

  <p>通过自己的系统（如我的Pentium 200 Pro，Netscape 3及JDK 1.1.5），这些相对时间向大家揭示出：新建对象和数组会造成最沉重的开销，同步会造成比较沉重的开销，而一次不同步的方法调用会造成适度的开销。参考资源[5]和[6]为大家总结了测量用程序片的Web地址，可到自己的机器上运行它们。</p>

  <p>1. 常规修改</p>

  <p>下面是加快Java程序关键部分执行速度的一些常规操作建议（注意对比修改前后的测试结果）。</p>

  <p>将... 修改成... 理由</p>

  <p>接口 抽象类（只需一个父时） 接口的多个继承会妨碍性能的优化</p>

  <p>非本地或数组循环变量 本地循环变量 根据前表的耗时比较，一次实例整数赋值的时间是本地整数赋值时间的1.2倍，但数组赋值的时间是本地整数赋值的2.7倍</p>

  <p>链接列表（固定尺寸） 保存丢弃的链接项目，或将列表替换成一个循环数组（大致知道尺寸） 每新建一个对象，都相当于本地赋值980次。参考“重复利用对象”（下一节）、Van Wyk[12] p.87以及Bentley[15] p.81</p>

  <p>x/2（或2的任意次幂） X&gt;&gt;2（或2的任意次幂） 使用更快的硬件指令</p>

  <p>D.3.3 特殊情况</p>

  <p>■字串的开销：字串连接运算符+看似简单，但实际需要消耗大量系统资源。编译器可高效地连接字串，但变量字串却要求可观的处理器时间。例如，假设s和t是字串变量：</p>

  <p>System.out.println("heading" + s + "trailer" + t);</p>

  <p>上述语句要求新建一个StringBuffer（字串缓冲），追加自变量，然后用toString()将结果转换回一个字串。因此，无论磁盘空间还是处理器时间，都会受到严重消耗。若准备追加多个字串，则可考虑直接使用一个字串缓冲――特别是能在一个循环里重复利用它的时候。通过在每次循环里禁止新建一个字串缓冲，可节省980单位的对象创建时间（如前所述）。利用substring()以及其他字串方法，可进一步地改善性能。如果可行，字符数组的速度甚至能够更快。也要注意由于同步的关系，所以StringTokenizer会造成较大的开销。</p>

  <p>■同步：在JDK解释器中，调用同步方法通常会比调用不同步方法慢10倍。经JIT编译器处理后，这一性能上的差距提升到50到100倍（注意前表总结的时间显示出要慢97倍）。所以要尽可能避免使用同步方法――若不能避免，方法的同步也要比代码块的同步稍快一些。</p>

  <p>■重复利用对象：要花很长的时间来新建一个对象（根据前表总结的时间，对象的新建时间是赋值时间的980倍，而新建一个小数组的时间是赋值时间的3100倍）。因此，最明智的做法是保存和更新老对象的字段，而不是创建一个新对象。例如，不要在自己的paint()方法中新建一个Font对象。相反，应将其声明成实例对象，再初始化一次。在这以后，可在paint()里需要的时候随时进行更新。参见Bentley编著的《编程拾贝》，p.81[15]。</p>

  <p>■异常：只有在不正常的情况下，才应放弃异常处理模块。什么才叫“不正常”呢？这通常是指程序遇到了问题，而这一般是不愿见到的，所以性能不再成为优先考虑的目标。进行优化时，将小的“try-catch”块合并到一起。由于这些块将代码分割成小的、各自独立的片断，所以会妨碍编译器进行优化。另一方面，若过份热衷于删除异常处理模块，也可能造成代码健壮程度的下降。</p>

  <p>■散列处理：首先，Java 1.0和1.1的标准“散列表”（Hashtable）类需要造型以及特别消耗系统资源的同步处理（570单位的赋值时间）。其次，早期的JDK库不能自动决定最佳的表格尺寸。最后，散列函数应针对实际使用项（Key）的特征设计。考虑到所有这些原因，我们可特别设计一个散列类，令其与特定的应用程序配合，从而改善常规散列表的性能。注意Java 1.2集合库的散列映射（HashMap）具有更大的灵活性，而且不会自动同步。</p>

  <p>■方法内嵌：只有在方法属于final（最终）、private（专用）或static（静态）的情况下，Java编译器才能内嵌这个方法。而且某些情况下，还要求它绝对不可以有局部变量。若代码花大量时间调用一个不含上述任何属性的方法，那么请考虑为其编写一个“final”版本。</p>

  <p>■I/O：应尽可能使用缓冲。否则，最终也许就是一次仅输入／输出一个字节的恶果。注意JDK 1.0的I/O类采用了大量同步措施，所以若使用象readFully()这样的一个“大批量”调用，然后由自己解释数据，就可获得更佳的性能。也要注意Java 1.1的“reader”和“writer”类已针对性能进行了优化。</p>

  <p>■造型和实例：造型会耗去2到200个单位的赋值时间。开销更大的甚至要求上溯继承（遗传）结构。其他高代价的操作会损失和恢复更低层结构的能力。</p>

  <p>■图形：利用剪切技术，减少在repaint()中的工作量；倍增缓冲区，提高接收速度；同时利用图形压缩技术，缩短下载时间。来自JavaWorld的“Java Applets”以及来自Sun的“Performing Animation”是两个很好的教程。请记着使用最贴切的命令。例如，为根据一系列点画一个多边形，和drawLine()相比，drawPolygon()的速度要快得多。如必须画一条单像素粗细的直线，drawLine(x,y,x,y)的速度比fillRect(x,y,1,1)快。</p>

  <p>■使用API类：尽量使用来自Java API的类，因为它们本身已针对机器的性能进行了优化。这是用Java难于达到的。比如在复制任意长度的一个数组时，arraryCopy()比使用循环的速度快得多。</p>

  <p>■替换API类：有些时候，API类提供了比我们希望更多的功能，相应的执行时间也会增加。因此，可定做特别的版本，让它做更少的事情，但可更快地运行。例如，假定一个应用程序需要一个容器来保存大量数组。为加快执行速度，可将原来的Vector（矢量）替换成更快的动态对象数组。</p>

  <p>1. 其他建议</p>

  <p>■将重复的常数计算移至关键循环之外――比如计算固定长度缓冲区的buffer.length。</p>

  <p>■static final（静态最终）常数有助于编译器优化程序。</p>

  <p>■实现固定长度的循环。</p>

  <p>■使用javac的优化选项：-O。它通过内嵌static，final以及private方法，从而优化编译过的代码。注意类的长度可能会增加（只对JDK 1.1而言――更早的版本也许不能执行字节查证）。新型的“Just-in-time”（JIT）编译器会动态加速代码。</p>

  <p>■尽可能地将计数减至0――这使用了一个特殊的JVM字节码。</p>

  <p>D.4 参考资源</p>

  <p>D.4.1 性能工具</p>

  <p>[1] 运行于Pentium Pro 200，Netscape 3.0，JDK 1.1.4的MicroBenchmark（参见下面的参考资源[5]）</p>

  <p>[2] Sun的Java文档页――JDK Java解释器主题：</p>

  <p>http://java.sun.com/products/JDK/tools/win32/java.html</p>

  <p>[3] Vladimir Bulatov的HyperProf</p>

  <p>http://www.physics.orst.edu/~bulatov/HyperProf</p>

  <p>[4] Greg White的ProfileViewer</p>

  <p>http://www.inetmi.com/~gwhi/ProfileViewer/ProfileViewer.html</p>

  <p>D.4.2 Web站点</p>

  <p>[5] 对于Java代码的优化主题，最出色的在线参考资源是Jonathan Hardwick的“Java Optimization”网站：</p>

  <p>http://www.cs.cmu.edu/~jch/java/optimization.html</p>

  <p>“Java优化工具”主页：</p>

  <p>http://www.cs.cmu.edu/~jch/java/tools.html</p>

  <p>以及“Java Microbenchmarks”（有一个45秒钟的评测过程）：</p>

  <p>http://www.cs.cmu.edu/~jch/java/benchmarks.html</p>

  <p>D.4.3 文章</p>

  <p>[6] “Make Java fast:Optimize! How to get the greatest performanceout of your code through low-level optimizations in Java”（让Java更快：优化！如何通过在Java中的低级优化，使代码发挥最出色的性能）。作者：Doug Bell。网址：</p>

  <p>http://www.javaworld.com/javaworld/jw-04-1997/jw-04-optimize.html</p>

  <p>（含一个全面的性能评测程序片，有详尽注释）</p>

  <p>[7] “Java Optimization Resources”（Java优化资源）</p>

  <p>http://www.cs.cmu.edu/~jch/java/resources.html</p>

  <p>[8] “Optimizing Java for Speed”（优化Java，提高速度）：</p>

  <p>http://www.cs.cmu.edu/~jch/java/speed.html</p>

  <p>[9] “An Empirical Study of FORTRAN Programs”（FORTRAN程序实战解析）。作者：Donald Knuth。1971年出版。第1卷，p.105-33，“软件――实践和练习”。</p>

  <p>[10] “Building High-Performance Applications and Servers in Java:An Experiential Study”。作者:Jimmy Nguyen，Michael Fraenkel，RichardRedpath，Binh Q. Nguyen以及Sandeep K. Singhal。IBM T.J. Watson ResearchCenter,IBM Software Solutions。</p>

  <p>http://www.ibm.com/java/education/javahipr.html</p>

  <p>D.4.4 Java专业书籍</p>

  <p>[11] 《Advanced Java，Idioms，Pitfalls，Styles, and Programming Tips》。作者：Chris Laffra。Prentice Hall 1997年出版（Java 1.0）。第11章第20小节。</p>

  <p>D.4.5 一般书籍</p>

  <p>[12] 《Data Structures and C Programs》（数据结构和C程序）。作者：J.Van Wyk。Addison-Wesly 1998年出版。</p>

  <p>[13] 《Writing Efficient Programs》（编写有效的程序）。作者：Jon Bentley。Prentice Hall 1982年出版。特别参考p.110和p.145-151。</p>

  <p>[14] 《More Programming Pearls》（编程拾贝第二版）。作者：JonBentley。“Association for Computing Machinery”，1998年2月。</p>

  <p>[15] 《Programming Pearls》（编程拾贝）。作者：Jone Bentley。Addison-Wesley 1989年出版。第2部分强调了常规的性能改善问题。 [16] 《Code Complete:A Practical Handbook of Software Construction》（完整代码索引：实用软件开发手册）。作者：Steve McConnell。Microsoft出版社1993年出版，第9章。</p>

  <p>[17] 《Object-Oriented System Development》（面向对象系统的开发）。作者：Champeaux，Lea和Faure。第25章。</p>

  <p>[18] 《The Art of Programming》（编程艺术）。作者：Donald Knuth。第1卷“基本算法第3版”；第3卷“排序和搜索第2版”。Addison-Wesley出版。这是有关程序算法的一本百科全书。</p>

  <p>[19] 《Algorithms in C:Fundammentals,Data Structures, Sorting,Searching》（C算法：基础、数据结构、排序、搜索）第3版。作者：RobertSedgewick。Addison-Wesley 1997年出版。作者是Knuth的学生。这是专门讨论几种语言的七个版本之一。对算法进行了深入浅出的解释。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>附录E 关于垃圾收集的一些话</p>

  <p>“很难相信Java居然能和C++一样快，甚至还能更快一些。”</p>

  <p>据我自己的实践，这种说法确实成立。然而，我也发现许多关于速度的怀疑都来自一些早期的实现方式。由于这些方式并非特别有效，所以没有一个模型可供参考，不能解释Java速度快的原因。</p>

  <p>我之所以想到速度，部分原因是由于C++模型。C++将自己的主要精力放在编译期间“静态”发生的所有事情上，所以程序的运行期版本非常短小和快速。C++也直接建立在C模型的基础上（主要为了向后兼容），但有时仅仅由于它在C中能按特定的方式工作，所以也是C++中最方便的一种方法。最重要的一种情况是C和C++对内存的管理方式，它是某些人觉得Java速度肯定慢的重要依据：在Java中，所有对象都必须在内存“堆”里创建。</p>

  <p>而在C++中，对象是在堆栈中创建的。这样可达到更快的速度，因为当我们进入一个特定的作用域时，堆栈指针会向下移动一个单位，为那个作用域内创建的、以堆栈为基础的所有对象分配存储空间。而当我们离开作用域的时候（调用完毕所有局部构建器后），堆栈指针会向上移动一个单位。然而，在C++里创建“内存堆”（Heap）对象通常会慢得多，因为它建立在C的内存堆基础上。这种内存堆实际是一个大的内存池，要求必须进行再循环（再生）。在C++里调用delete以后，释放的内存会在堆里留下一个洞，所以再调用new的时候，存储分配机制必须进行某种形式的搜索，使对象的存储与堆内任何现成的洞相配，否则就会很快用光堆的存储空间。之所以内存堆的分配会在C++里对性能造成如此重大的性能影响，对可用内存的搜索正是一个重要的原因。所以创建基于堆栈的对象要快得多。</p>

  <p>同样地，由于C++如此多的工作都在编译期间进行，所以必须考虑这方面的因素。但在Java的某些地方，事情的发生却要显得“动态”得多，它会改变模型。创建对象的时候，垃圾收集器的使用对于提高对象创建的速度产生了显著的影响。从表面上看，这种说法似乎有些奇怪――存储空间的释放会对存储空间的分配造成影响，但它正是JVM采取的重要手段之一，这意味着在Java中为堆对象分配存储空间几乎能达到与C++中在堆栈里创建存储空间一样快的速度。</p>

  <p>可将C++的堆（以及更慢的Java堆）想象成一个庭院，每个对象都拥有自己的一块地皮。在以后的某个时间，这种“不动产”会被抛弃，而且必须再生。但在某些JVM里，Java堆的工作方式却是颇有不同的。它更象一条传送带：每次分配了一个新对象后，都会朝前移动。这意味着对象存储空间的分配可以达到非常快的速度。“堆指针”简单地向前移至处女地，所以它与C++的堆栈分配方式几乎是完全相同的（当然，在数据记录上会多花一些开销，但要比搜索存储空间快多了）。</p>

  <p>现在，大家可能注意到了堆事实并非一条传送带。如按那种方式对待它，最终就要求进行大量的页交换（这对性能的发挥会产生巨大干扰），这样终究会用光内存，出现内存分页错误。所以这儿必须采取一个技巧，那就是著名的“垃圾收集器”。它在收集“垃圾”的同时，也负责压缩堆里的所有对象，将“堆指针”移至尽可能靠近传送带开头的地方，远离发生（内存）分页错误的地点。垃圾收集器会重新安排所有东西，使其成为一个高速、无限自由的堆模型，同时游刃有余地分配存储空间。</p>

  <p>为真正掌握它的工作原理，我们首先需要理解不同垃圾收集器（GC）采取的工作方案。一种简单、但速度较慢的GC技术是引用计数。这意味着每个对象都包含了一个引用计数器。每当一个句柄同一个对象连接起来时，引用计数器就会增值。每当一个句柄超出自己的作用域，或者设为null时，引用计数就会减值。这样一来，只要程序处于运行状态，就需要连续进行引用计数管理――尽管这种管理本身的开销比较少。垃圾收集器会在整个对象列表中移动巡视，一旦它发现其中一个引用计数成为0，就释放它占据的存储空间。但这样做也有一个缺点：若对象相互之间进行循环引用，那么即使引用计数不是0，仍有可能属于应收掉的“垃圾”。为了找出这种自引用的组，要求垃圾收集器进行大量额外的工作。引用计数属于垃圾收集的一种类型，但它看起来并不适合在所有JVM方案中采用。</p>

  <p>在速度更快的方案里，垃圾收集并不建立在引用计数的基础上。相反，它们基于这样一个原理：所有非死锁的对象最终都肯定能回溯至一个句柄，该句柄要么存在于堆栈中，要么存在于静态存储空间。这个回溯链可能经历了几层对象。所以，如果从堆栈和静态存储区域开始，并经历所有句柄，就能找出所有活动的对象。对于自己找到的每个句柄，都必须跟踪到它指向的那个对象，然后跟随那个对象中的所有句柄，“跟踪追击”到它们指向的对象……等等，直到遍历了从堆栈或静态存储区域中的句柄发起的整个链接网路为止。中途移经的每个对象都必须仍处于活动状态。注意对于那些特殊的自引用组，并不会出现前述的问题。由于它们根本找不到，所以会自动当作垃圾处理。</p>

  <p>在这里阐述的方法中，JVM采用一种“自适应”的垃圾收集方案。对于它找到的那些活动对象，具体采取的操作取决于当前正在使用的是什么变体。其中一个变体是“停止和复制”。这意味着由于一些不久之后就会非常明显的原因，程序首先会停止运行（并非一种后台收集方案）。随后，已找到的每个活动对象都会从一个内存堆复制到另一个，留下所有的垃圾。除此以外，随着对象复制到新堆，它们会一个接一个地聚焦在一起。这样可使新堆显得更加紧凑（并使新的存储区域可以简单地抽离末尾，就象前面讲述的那样）。</p>

  <p>当然，将一个对象从一处挪到另一处时，指向那个对象的所有句柄（引用）都必须改变。对于那些通过跟踪内存堆的对象而获得的句柄，以及那些静态存储区域，都可以立即改变。但在“遍历”过程中，还有可能遇到指向这个对象的其他句柄。一旦发现这个问题，就当即进行修正（可想象一个散列表将老地址映射成新地址）。</p>

  <p>有两方面的问题使复制收集器显得效率低下。第一个问题是我们拥有两个堆，所有内存都在这两个独立的堆内来回移动，要求付出的管理量是实际需要的两倍。为解决这个问题，有些JVM根据需要分配内存堆，并将一个堆简单地复制到另一个。</p>

  <p>第二个问题是复制。随着程序变得越来越“健壮”，它几乎不产生或产生很少的垃圾。尽管如此，一个副本收集器仍会将所有内存从一处复制到另一处，这显得非常浪费。为避免这个问题，有些JVM能侦测是否没有产生新的垃圾，并随即改换另一种方案（这便是“自适应”的缘由）。另一种方案叫作“标记和清除”，Sun公司的JVM一直采用的都是这种方案。对于常规性的应用，标记和清除显得非常慢，但一旦知道自己不产生垃圾，或者只产生很少的垃圾，它的速度就会非常快。</p>

  <p>标记和清除采用相同的逻辑：从堆栈和静态存储区域开始，并跟踪所有句柄，寻找活动对象。然而，每次发现一个活动对象的时候，就会设置一个标记，为那个对象作上“记号”。但此时尚不收集那个对象。只有在标记过程结束，清除过程才正式开始。在清除过程中，死锁的对象会被释放然而，不会进行任何形式的复制，所以假若收集器决定压缩一个断续的内存堆，它通过移动周围的对象来实现。</p>

  <p>“停止和复制”向我们表明这种类型的垃圾收集并不是在后台进行的；相反，一旦发生垃圾收集，程序就会停止运行。在Sun公司的文档库中，可发现许多地方都将垃圾收集定义成一种低优先级的后台进程，但它只是一种理论上的实验，实际根本不能工作。在实际应用中，Sun的垃圾收集器会在内存减少时运行。除此以外，“标记和清除”也要求程序停止运行。</p>

  <p>正如早先指出的那样，在这里介绍的JVM中，内存是按大块分配的。若分配一个大块头对象，它会获得自己的内存块。严格的“停止和复制”要求在释放旧堆之前，将每个活动的对象从源堆复制到一个新堆，此时会涉及大量的内存转换工作。通过内存块，垃圾收集器通常可利用死块复制对象，就象它进行收集时那样。每个块都有一个生成计数，用于跟踪它是否依然“存活”。通常，只有自上次垃圾收集以来创建的块才会得到压缩；对于其他所有块，如果已从其他某些地方进行了引用，那么生成计数都会溢出。这是许多短期的、临时的对象经常遇到的情况。会周期性地进行一次完整清除工作――大块头的对象仍未复制（只是让它们的生成计数溢出），而那些包含了小对象的块会进行复制和压缩。JVM会监视垃圾收集器的效率，如果由于所有对象都属于长期对象，造成垃圾收集成为浪费时间的一个过程，就会切换到“标记和清除”方案。类似地，JVM会跟踪监视成功的“标记与清除”工作，若内存堆变得越来越“散乱”，就会换回“停止和复制”方案。“自定义”的说法就是从这种行为来的，我们将其最后总结为：“根据情况，自动转换停止和复制／标记和清除这两种模式”。</p>

  <p>JVM还采用了其他许多加速方案。其中一个特别重要的涉及装载器以及JIT编译器。若必须装载一个类（通常是我们首次想创建那个类的一个对象时），会找到.class文件，并将那个类的字节码送入内存。此时，一个方法是用JIT编译所有代码，但这样做有两方面的缺点：它会花更多的时间，若与程序的运行时间综合考虑，编译时间还有可能更长；而且它增大了执行文件的长度（字节码比扩展过的JIT代码精简得多），这有可能造成内存页交换，从而显著放慢一个程序的执行速度。另一种替代办法是：除非确有必要，否则不经JIT编译。这样一来，那些根本不会执行的代码就可能永远得不到JIT的编译。</p>

  <p>由于JVM对浏览器来说是外置的，大家可能希望在使用浏览器的时候从一些JVM的速度提高中获得好处。但非常不幸，JVM目前不能与不同的浏览器进行沟通。为发挥一种特定JVM的潜力，要么使用内建了那种JVM的浏览器，要么只有运行独立的Java应用程序。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>附录F 推荐读物</p>

  <p>■《Java in a Nutshell:A Desktop Quick Reference，第2版》</p>

  <p>作者：David Flanagan</p>

  <p>出版社：O'Reilly &amp; Assoc</p>

  <p>出版时间：1997</p>

  <p>简介：对Java 1.1联机文档的一个简要总结。就个人来说，我更喜欢在线阅览文档，特别是在它们变化得如此快的时候。然而，许多人仍然喜欢印刷出来的文档，这样可以省一些上网费。而且这本书也提供了比联机文档更多的讨论。</p>

  <p>■《The Java Class Libraries:An Annotated Reference》</p>

  <p>作者：Patrick Chan和Rosanna Lee</p>

  <p>出版社：Addison-Wesley</p>

  <p>出版时间：1997</p>

  <p>简介：作为一种联机参考资源，应向读者提供足够多的说明，使其简单易用。《Thinking in Java》的一名技术审定员说道：“如果我只能有一本Java书，那么肯定选它。”不过我可没有他那么激动。它太大、太贵，而且示例的质量并不能令我满意。但在遇到麻烦的时候，该书还是很有参考价值的。而且与《Java in a Nutshell》相比，它看起来有更大的深度（当然也有更多的文字）。</p>

  <p>■《Java Network Programming》</p>

  <p>作者：Elliote Rusty Harold</p>

  <p>David Flanagan</p>

  <p>出版社：O'Reilly</p>

  <p>出版时间：1997</p>

  <p>简介：在阅读本书前，我可以说根本不理解Java有关网络的问题。后来，我也发现他的Web站点“Cafe au Lait”是个令人激动的、很人个性的以及经常更新的去处，涉及大量有价值的Java开发资源。由于几乎每天更新，所以在这里能看到与Java有关的大量新闻。站点地址是：http://sunsite.unc.edu/javafaq/。</p>

  <p>■《Core Java，第3版》</p>

  <p>作者：Cornel和Horstmann</p>

  <p>出版社：Prentice-Hall</p>

  <p>出版时间：1997</p>

  <p>简介：对于自己碰到的问题，若在《Thinking in Java》里找不到答案，这就是一个很好的参考地点。注意：Java 1.1的版本是《Core Java 1.1 Volume 1-Fundamentals &amp; Core Java 1.1 Volume 2-Advanced Features》</p>

  <p>■《JDBC Database Access with Java》</p>

  <p>作者：Hamilton，Cattell和Fisher</p>

  <p>出版社：Addison-Wesley</p>

  <p>出版时间：1997</p>

  <p>简介：如果对SQL和数据库一无所知，这本书就可以作为一个相当好的起点。它也对API进行了详尽的解释，并提供一个“注释参考。与“Java系列”（由JavaSoft授权的唯一一套丛书）的其他所有书籍一样，这本书的缺点也是进行了过份的渲染，只说Java的好话――在这一系列书籍里找不到任何不利于Java的地方。</p>

  <p>■《Java Programming with CORBA》</p>

  <p>作者：Andreas Vogel和Keith Duddy</p>

  <p>出版社：Jonh Wiley &amp; Sons</p>

  <p>出版时间：1997</p>

  <p>简介：针对三种主要的Java ORB（Visbroker，Orbix，Joe），本书分别用大量代码实例进行了详尽的阐述。</p>

  <p>■《Design Patterns》</p>

  <p>作者：Gamma，Helm，Johnson和Vlissides</p>

  <p>出版社：Addison-Wesley</p>

  <p>出版时间：1995</p>

  <p>简介：这是一本发起了编程领域方案革命的经典书籍。</p>

  <p>■《UML Toolkit》</p>

  <p>作者：Hans-Erik Eriksson和Magnus Penker</p>

  <p>出版社：Jonh Wiley &amp; Sons</p>

  <p>出版时间：1997</p>

  <p>简介：解释UML以及如何使用它，并提供Java的实际案例供参考。配套CD-ROM包含了Java代码以及Rational Rose的一个删减版本。本书对UML进行了非常出色的描述，并解释了如何用它构建实际的系统。</p>

  <p>■《Practical Algorithms for Programmers》</p>

  <p>作者：Binstock和Rex</p>

  <p>出版社：Addison-Wesley</p>

  <p>出版时间：1995</p>

  <p>简介：算法是用C描述的，所以它们很容易就能转换到Java里面。每种算法都有详尽的解释。</p>

  <p>更多免费txt电子书，欢迎您到www.16txt.com下载</p>

  <p>声明：本电子书仅供读者预览,请在下载24小时内删除，不得用作商业用途；如果喜欢请购买正版图书！</p>

  <p>申明:本书由一流TXT小说下载网(http://ww.16txt.com)自网络收集整理制作,仅供预览交流学习使用,版权归原作者和出版社所有,如果喜欢,请支持订阅购买正版。</p>

  <p>更多精彩TXT电子书，请登陆一流TXT小说下载网--http://www.16txt.com</p>

  <div class="mbppagebreak"></div>
</body>
</html>
